page.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import { useRouter, useParams } from 'next/navigation';
  4. import Link from 'next/link';
  5. import { fetchApi } from '@/lib/utils/client';
  6. import { useStudioContext } from '@/app/studio/context';
  7. import { useGoalConfigContext } from '../../context';
  8. import { Separator } from '@/components/ui/separator';
  9. import GoalPreviewPanel from '../../_components/GoalPreviewPanel';
  10. import GoalFormPanel from '../../_components/GoalFormPanel';
  11. import { createEmptyForm, formatInput, parseInput } from '../../types';
  12. import type { FormState } from '../../types';
  13. import type { GoalConfigItem } from '@/types/response/donation/goalConfig';
  14. export default function GoalEditPage()
  15. {
  16. const router = useRouter();
  17. const { id } = useParams<{ id: string }>();
  18. const numericId = parseInt(id);
  19. const { channelID } = useStudioContext();
  20. const { items, loading, setSaving } = useGoalConfigContext();
  21. const [editingItem, setEditingItem] = useState<GoalConfigItem|null>(null);
  22. const [form, setForm] = useState<FormState>(createEmptyForm());
  23. const [formInitialized, setFormInitialized] = useState(false);
  24. const [localSaving, setLocalSaving] = useState(false);
  25. // ── items 로드 후 form 초기화 ────────────────────
  26. useEffect(() => {
  27. if (formInitialized || items.length === 0) {
  28. return;
  29. }
  30. const found = items.find(item => item.id === numericId);
  31. if (found) {
  32. setEditingItem(found);
  33. setForm({
  34. title: found.title,
  35. style: found.style,
  36. startAmount: found.startAmount,
  37. targetAmount: found.targetAmount,
  38. startAt: found.startAt ? formatInput(new Date(found.startAt)) : '',
  39. endAt: found.endAt ? formatInput(new Date(found.endAt)) : '',
  40. isShowPercent: found.isShowPercent,
  41. barColor: found.barColor,
  42. barBackgroundColor: found.barBackgroundColor,
  43. barHeightPx: found.barHeightPx,
  44. titleFontFamily: found.titleFontFamily,
  45. titleFontSizePx: found.titleFontSizePx,
  46. titleFontColor: found.titleFontColor,
  47. amountFontFamily: found.amountFontFamily,
  48. amountFontSizePx: found.amountFontSizePx,
  49. amountFontColor: found.amountFontColor,
  50. isActive: found.isActive
  51. });
  52. setFormInitialized(true);
  53. } else if (!loading) {
  54. alert('목표 설정을 찾을 수 없습니다.');
  55. router.push('/studio/donation/goal/list');
  56. }
  57. }, [items, loading, numericId, formInitialized, router]);
  58. // ── 폼 필드 변경 ────────────────────────────────
  59. const handleFormChange = <K extends keyof FormState>(field: K, value: FormState[K]) => {
  60. setForm(prev => ({ ...prev, [field]: value }));
  61. };
  62. // ── 저장 ─────────────────────────────────────────
  63. const handleSave = async () => {
  64. if (!channelID || !editingItem) {
  65. return;
  66. }
  67. if (!form.title.trim()) {
  68. alert('제목을 입력해 주세요.');
  69. return;
  70. }
  71. if (form.targetAmount < 1) {
  72. alert('목표금액은 1원 이상이어야 합니다.');
  73. return;
  74. }
  75. setLocalSaving(true);
  76. setSaving(true);
  77. try {
  78. await fetchApi('/api/studio/donation/goal/config', {
  79. method: 'POST',
  80. body: {
  81. channelID,
  82. id: editingItem.id,
  83. ...form,
  84. startAt: parseInput(form.startAt ?? '') || undefined,
  85. endAt: parseInput(form.endAt ?? '') || undefined,
  86. titleFontFamily: form.titleFontFamily || null,
  87. amountFontFamily: form.amountFontFamily || null
  88. }
  89. });
  90. alert('수정되었습니다.');
  91. } catch (err) {
  92. alert(err instanceof Error ? err.message : '저장에 실패했습니다.');
  93. } finally {
  94. setLocalSaving(false);
  95. setSaving(false);
  96. }
  97. };
  98. // ── 취소 ─────────────────────────────────────────
  99. const handleCancel = () => {
  100. router.push('/studio/donation/goal/list');
  101. };
  102. // ── 로딩 중 ──────────────────────────────────────
  103. if (!formInitialized) {
  104. return <div className="goal-config__loading">준비 중...</div>;
  105. }
  106. return (
  107. <>
  108. <div className="studio-page__title-row">
  109. <h1 className="studio-page__title">후원 목표 수정</h1>
  110. <Link href="/studio/donation/goal/list" className="goal-config__btn goal-config__btn--sm">< 목록으로</Link>
  111. </div>
  112. <div className="pt-5 pb-5">
  113. <Separator orientation="horizontal" />
  114. </div>
  115. <div className="goal-config__layout">
  116. <GoalPreviewPanel form={form} />
  117. <Separator orientation="vertical" />
  118. <GoalFormPanel
  119. form={form}
  120. editingItem={editingItem}
  121. saving={localSaving}
  122. onFormChange={handleFormChange}
  123. onSave={handleSave}
  124. onCancel={handleCancel}
  125. />
  126. </div>
  127. </>
  128. );
  129. }